/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package cae;

import entities.User;
import entities.Problem;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import vo.VOSubmission;
import vo.VOTestcase;

/**
 *
 * @author Niquefa
 */
public class WindowsCAE
{
    private static final boolean DEEP_DEBUG = false;
    private int maxExcecutionTimeInMiliSeconds = -1;
    private int problemTimeLimitInSeconds = 3;
    private long auxTime;
    private String auxVerdict;
    //Variable auxiliar para "matar" 
    private Process proc;            
    private ArrayList<VOTestcase> testCases;
    private VOSubmission submission;
    private String languaje;
    private String sourceCode;    
    private EnumVerdicts verdict = EnumVerdicts.NOT_YET_COMPILED;
    private EnumStatus status = EnumStatus.NOT_COMPILED;
    private boolean compilationSuccessfull = false;
    private String compilationOutput = EnumStatus.NOT_COMPILED.value();
    private String executionOutput = EnumStatus.NOT_COMPILED.value();

    private WindowsCAE(){}
    
    public WindowsCAE( int problemTimeLimitInSeconds , ArrayList<VOTestcase> testCases, VOSubmission submission )
    {
        this.problemTimeLimitInSeconds = problemTimeLimitInSeconds;
        this.submission = submission;
        this.testCases = testCases;
        this.languaje = submission.getLanguage();
        this.sourceCode = submission.getCode();        
    }    
    public WindowsCAE( Problem problem , ArrayList<VOTestcase> testCases, VOSubmission submission )
    {
        this.problemTimeLimitInSeconds = problem.getTime();
        this.submission = submission;
        this.testCases = testCases;
        this.languaje = submission.getLanguage();
        this.sourceCode = submission.getCode();        
    }
    public void compileScript()
    {
        if( createSourceFile() )
        {
            compilationSuccessfull = this.compile();
            if( compilationSuccessfull )
            {
                this.verdict = EnumVerdicts.COMPILED_METHOD_NOT_EXECUTED;
                this.status = EnumStatus.UNSUCCESSFULLY_COMPILED;
            }
            else
            {
                this.verdict = EnumVerdicts.COMPILATION_ERROR;
                this.status = EnumStatus.SUCCESSFULLY_COMPILED;
            }
        }
        else
        {
            this.status = EnumStatus.SOURCE_CODE_ERROR;
        }        
    }
    public boolean isSuccessfullyCompiled()
    {
        return this.compilationSuccessfull;
    }
    public static void seeWorkSpaceDir()throws Exception
    {
        Runtime rt = Runtime.getRuntime();
        Process p=rt.exec("cmd.exe /C dir");

        InputStream es = p.getInputStream();
        BufferedReader br = new BufferedReader (new InputStreamReader (es));
        String aux;
        String salida = "";
        while ( (aux = br.readLine())!=null )
            salida += aux+"\n";

        System.out.println("(seeWorkSpaceDir)SALIDA \n"+salida);
    }
    public static void killProcess( String processName )throws Exception
    {
        //TO DO
        if( processName.indexOf("java.exe") >= 0)return;
        Runtime rt = Runtime.getRuntime();
        Process p=rt.exec("cmd.exe /C taskkill /F /IM "+processName+"");
        InputStream es = p.getInputStream();
        BufferedReader br = new BufferedReader (new InputStreamReader (es));
        String aux;
        String salida = "";
        while ( (aux = br.readLine())!=null )
            salida += aux+"\n";

        if(DEEP_DEBUG)System.out.println("(killProcess)SALIDA \n"+salida);
    }
    public static void main( String args[] )throws Exception
    {
        if(DEEP_DEBUG)seeWorkSpaceDir();
        String javaCode = "import java.io.*;\n"+
                    "import java.util.StringTokenizer;\n"+
                    "public class Main \n"+
                    "{\n"+
                    "        public static void main(String[] args)throws Exception\n"+
                    "        {\n"+
                    "                for(int i = 0; i < 0; ++i ){++i;--i;++i;--i;} \n"+
                    "                StringTokenizer ts = new StringTokenizer((new BufferedReader( new InputStreamReader( System.in ) )).readLine());\n"+
                    "                System.out.println(Integer.parseInt(ts.nextToken())+Integer.parseInt(ts.nextToken())+Integer.parseInt(ts.nextToken())); \n"+
                    "        }\n"+
                    "}";

        String code = "#include <iostream>\n"+
                            "using namespace std;\n"+
                            "int main()\n"+
                            "{for(int i = 0; i < 0; ++i ){++i;--i;++i;--i;if(i%10000000 != 0 )cout << i << endl;} \n"+
                               "\tint a , b , c;\n"+
                               "\tcin >> a >> b >> c;\n"+
                               "\tcout << a + b + c <<endl;int cc = 3.3;cc >?= 32;\n"+
                               "\treturn 0;\n"+
                            "}\n";
        Problem problem = new Problem();
        problem.setName("Suma 3 numeros");
        problem.setDescription("Lea una linea con tres números y realice la suma de ellos, imprima el resultado");
        problem.setDifficulty(2);
        problem.setTime(1);
        problem.setIdProblem(1111111111);
        
        ArrayList<VOTestcase> testCases = new ArrayList<VOTestcase>();
        for (int i = 0; i < 10; i++)
        {
            testCases.add( new VOTestcase() );
            testCases.get( i ).setInput(""+(i+1)+" "+(i+2)+" "+(i+3));
            testCases.get( i ).setOutput(""+(3*i+6));
            testCases.get( i ).setIdTestCase(1000000000+i);            
        }
        User user = new User();
        user.setIdUser(10000001);
        VOSubmission submission = new VOSubmission();
        submission.setCode(code);
        submission.setUserHandle( user.getHandle() );
        submission.setLanguage(cae.EnumProgrammingLanguages.CPP.value());
        //WindowsCAE cae = new WindowsCAE(problem.getTime(),testCases,submission);
        WindowsCAE cae = new WindowsCAE(problem,testCases,submission);
        cae.compileScript();
        if( cae.isSuccessfullyCompiled() )
        {
            cae.runTestCases();
            System.out.println("COMPILACION exitosa");
            System.out.println("COMPILATION OUTPUT:");
            System.out.println(cae.getCompilationOutput());
            System.out.println("EXECUTION OUTPUT:");
            System.out.println(cae.getExecutionOutput());                    
            System.out.println("VERDICT:");
            System.out.println(cae.getVerdict().value());
            System.out.println("Total Time: "+cae.getMaxExcecutionTimeInMiliSeconds()+" miliseconds");            
        }
        else
        {
            System.out.println("COMPILATION ERROR");
            System.out.println(cae.compilationOutput);
        }
        if(DEEP_DEBUG)System.out.println("Revisar estos kill:WindowsCAE.killProcess(\"java.exe\");WindowsCAE.killProcess(\"a.exe\");");
        WindowsCAE.killProcess("java.exe");
        WindowsCAE.killProcess("a.exe");
    }
    private String getCompilationInstruction()
    {
        if( this.languaje.equalsIgnoreCase( cae.EnumProgrammingLanguages.CPP.value() ) ) return "cmd.exe /C g++ "+EnumFileNamesWindows.CPP_SOURCE_CODE_NAME.value();
        return "cmd.exe /C javac "+EnumFileNamesWindows.JAVA_SOURCE_CODE_NAME.value();
    }
    private String getExecutionInstruction()
    {
        if( this.languaje.equalsIgnoreCase( cae.EnumProgrammingLanguages.CPP.value() ) ) return "cmd.exe /C "+EnumFileNamesWindows.EXE_FILE_NAME.value()+" < "+EnumFileNamesWindows.INPUT_FILE_NAME.value()+" > "+EnumFileNamesWindows.OUTPUT_FILE_NAME.value()+"";
        return "cmd.exe /C java "+EnumFileNamesWindows.CLASS_FILE_NAME.value()+" < "+EnumFileNamesWindows.INPUT_FILE_NAME.value()+" > "+EnumFileNamesWindows.OUTPUT_FILE_NAME.value()+"";
    }
    private boolean compile()
    {
        try
        {
            Runtime rt = Runtime.getRuntime();
            Process p=rt.exec (getCompilationInstruction());
            InputStream es = p.getErrorStream();
            BufferedReader br = new BufferedReader (new InputStreamReader (es));
            String aux;
            this.compilationOutput = "";
            while ( (aux = br.readLine())!=null )
                compilationOutput += aux+"\n";

            if( this.languaje.equals(cae.EnumProgrammingLanguages.CPP.value()))
            {
                File file = new File(EnumFileNamesWindows.EXE_FILE_NAME.value());
                if( !file.exists() )
                    return false;
            }
            else
            {
                File file = new File(EnumFileNamesWindows.CLASS_FILE_NAME.value()+".class");
                if( !file.exists() )
                    return false;
            }
            return true;
        }
        catch( Exception e )
        {
            System.out.println("Excepción en WindowsCAE.compile() ");
            return false;
        }
    }
    private void deleteFiles() 
    {
        for (EnumFileNamesWindows o : EnumFileNamesWindows.values())
        {
            File file = new File( o.value() );
            file.delete();
        }
        File file = new File( EnumFileNamesWindows.CLASS_FILE_NAME.value()+".class" );
        file.delete();
        file = new File( "a.out" );
        file.delete();
    }
    private boolean createSourceFile()
    {
        return createFile( this.languaje.equalsIgnoreCase(cae.EnumProgrammingLanguages.CPP.value()) ? EnumFileNamesWindows.CPP_SOURCE_CODE_NAME.value() : EnumFileNamesWindows.JAVA_SOURCE_CODE_NAME.value(), sourceCode );
    }
    private boolean createInputFile( String input )
    {
        return createFile( EnumFileNamesWindows.INPUT_FILE_NAME.value() , input );
    }
    private boolean createJudgeOutputFile( String output )
    {
        return createFile( EnumFileNamesWindows.JUDGE_OUTPUT_FILE_NAME.value() , output );
    }
    private static boolean createFile( String fileName , String content )
    {
        try
        {
            PrintWriter sourceCode = new PrintWriter(new BufferedWriter(new FileWriter(fileName)));
            sourceCode.println(content);
            sourceCode.close();
            return true;
        }
        catch (Exception e)
        {
            System.err.println("Exception in WindowsCAE.createFile("+fileName+")");
            return false;
        }
    }

    public EnumVerdicts runTestCases()
    {
        if(DEEP_DEBUG)System.out.println("en WindowsCAE al inicio");

        if( !this.compilationSuccessfull )return EnumVerdicts.COMPILATION_ERROR;      
        this.status = EnumStatus.RUNNING;
        int tle = 0;
        int wa = 0;
        int re = 0;
        int ue = 0;
        int pe = 0;
        for (int i = 0; i < testCases.size(); i++)
        {
            if(DEEP_DEBUG)System.out.println("EJECUTANDO TESTCASE  "+i+" ---------------------->>>>");
            String testCaseVerdict = EnumStatus.RUNNING.value();
            VOTestcase testcase = testCases.get(i);
            if( !createInputFile(testcase.getInput()) )
            {
                testCaseVerdict = EnumVerdicts.UNKOWN_ERROR.value();
                ++ue;
            }
            else if( !createJudgeOutputFile(testcase.getOutput()) )
            {
                testCaseVerdict = EnumVerdicts.UNKOWN_ERROR.value();
                ++ue;
            }
            else
            {
                if(DEEP_DEBUG)System.out.println("ANTES DE this.runTestCase()");
                long inicio = System.currentTimeMillis();
                testCaseVerdict = this.runTestCase();
                long totalTimeMilis = System.currentTimeMillis()-inicio;
                if(DEEP_DEBUG)System.out.println("Time TestCase: "+totalTimeMilis+" miliseconds");
                this.maxExcecutionTimeInMiliSeconds = (int) Math.max( totalTimeMilis, this.maxExcecutionTimeInMiliSeconds);
                if(DEEP_DEBUG)System.out.println("DESPUES DE this.runTestCase()");
                if( testCaseVerdict.equalsIgnoreCase( EnumVerdicts.TIME_LIMIT_EXCEEDED.value() ) )                
                   ++tle;
                else if( testCaseVerdict.equalsIgnoreCase( EnumVerdicts.RUN_TIME_ERROR.value() ) )                
                    ++re;
                else if( testCaseVerdict.equalsIgnoreCase( EnumVerdicts.WRONG_ANSWER.value() ) )
                    ++wa;
                else if( testCaseVerdict.equalsIgnoreCase( EnumVerdicts.PRESENTATION_ERROR.value() ) )
                    ++pe;
                
                if(DEEP_DEBUG)System.out.println("despues de los if!!!");
                Run run = new Run();
                if( tle == 0 && re == 0 )
                    run.setOutput( this.readUserOutput() );
                else
                    run.setOutput("ERROR: "+testCaseVerdict);
                run.setVerdict(testCaseVerdict);
                run.setTime((int) auxTime);
                
                if( tle + re + ue >= 2 )
                    break;
                if(DEEP_DEBUG)System.out.println(i+","+tle+","+re+","+ue+","+wa+" i,tle,re,ue,wa");
                if(DEEP_DEBUG)System.out.println("run agregado!!!"+i);
            }
        }
        this.status = EnumStatus.SUCCESSFULLY_JUDGED;
        this.deleteFiles();
        if(DEEP_DEBUG)System.out.println("en WindowsCAE al final");
        if( ue > 0 )
            this.verdict = EnumVerdicts.UNKOWN_ERROR;
        else if ( re > 0 )
            this.verdict = EnumVerdicts.RUN_TIME_ERROR;
        else if ( tle > 0 )
            this.verdict = EnumVerdicts.TIME_LIMIT_EXCEEDED;
        else if ( wa > 0 )
            this.verdict = EnumVerdicts.WRONG_ANSWER;
        else if( pe > 0 )
            this.verdict = EnumVerdicts.PRESENTATION_ERROR;
        else
            this.verdict = EnumVerdicts.ACCEPTED;
        if(DEEP_DEBUG)System.out.println("en WindowsCAE al final verdict = "+this.verdict);

        return this.verdict;
    }
    private String readUserOutput()
    {
        String ret = "";
        ArrayList<String> file = WindowsCAE.readFile( EnumFileNamesWindows.OUTPUT_FILE_NAME.value() );
        for (String string : file) {
            ret+=string+"\n";
        }
        return ret;
    }    
    private String runTestCase()
    {
        RunThread rt = new RunThread();
        
        int sleep_time = 100;
        int lim = (1000*this.problemTimeLimitInSeconds)/sleep_time;
        int i;
        long inicio = System.currentTimeMillis();
        auxVerdict = "";
        rt.start();
        for (i = 0; rt.isAlive() && i < lim; i++)
        {
            try {
                Thread.sleep(sleep_time);
            } catch (Exception ex) {
                System.err.println("Exception in WindowsCAE.run()");
            }
        }
        if(DEEP_DEBUG)System.out.println("I = "+i+" LIM = "+lim);
        rt.stop();
        rt.suspend();
        proc.destroy();
        if(DEEP_DEBUG)System.out.println("DESPUES DE STOP");
        try
        {
            WindowsCAE.killProcess(this.languaje.equalsIgnoreCase(cae.EnumProgrammingLanguages.CPP.value()) ? "a.exe" : "java.exe");
            if(DEEP_DEBUG)System.out.println("DESPUES DE killl");
        }
        catch( Exception e )
        {
            System.out.println("Exception en WindowsCAE.runTestCase()");
        }
        if(DEEP_DEBUG)System.out.println("Después del tryCatch de kill");
        auxTime = System.currentTimeMillis()-inicio;
        if(DEEP_DEBUG)System.out.println("TIEMPO DE EJECUCION: "+auxTime);
        if( i >= lim )
            return EnumVerdicts.TIME_LIMIT_EXCEEDED.value();


        return auxVerdict = this.compareOutputs();
    }
    private class RunThread extends Thread
    {
        private boolean continuar = true;
        public boolean getContinuar() { return continuar; }
        public void stopThread(){continuar=false;}
        @Override public void run()
        {
            auxVerdict = runn();
            this.stopThread();
        }
    }
    private String runn()
    {
        try
        {
            Runtime rt =Runtime.getRuntime();
            proc=rt.exec (getExecutionInstruction());

            InputStream es = proc.getErrorStream();
            BufferedReader br = new BufferedReader (new InputStreamReader (es));
            String aux;
            this.executionOutput = "";
            while ( (aux = br.readLine())!=null )
                executionOutput += aux+"\n";
            return this.compareOutputs();
        }
        catch( Exception e )
        {
            System.err.println("Exception in WindowsCAE.run()");
            return EnumVerdicts.RUN_TIME_ERROR.value();
        }
    }
    private String compareOutputs()
    {        
        ArrayList<String> judge = readFile(EnumFileNamesWindows.JUDGE_OUTPUT_FILE_NAME.value());
        ArrayList<String> contestant = readFile(EnumFileNamesWindows.OUTPUT_FILE_NAME.value());
        if(DEEP_DEBUG)System.out.println("En WindowsCAE.compareOutputs "+judge.size()+" =? "+contestant.size());

        if( judge == null || contestant == null )
            return EnumVerdicts.UNKOWN_ERROR.value();

        if( judge.size() != contestant.size() )
            return EnumVerdicts.WRONG_ANSWER.value();
        boolean AC = true;
        boolean PE = true;
        for (int i = 0; i < judge.size(); i++)
        {
            AC = AC && judge.get(i).equals(contestant.get(i));
            PE = PE && judge.get(i).trim().equals(contestant.get(i).trim());
            if( (!AC) && (!PE) )
            {
                if(DEEP_DEBUG)System.out.println("en WindowsCAE.java.compareOuputs NOT OK");
                if(DEEP_DEBUG)System.out.println("_"+judge.get(i)+"_"+contestant.get(i)+"_");
                return EnumVerdicts.WRONG_ANSWER.value();
            }
            else
            {
                if(DEEP_DEBUG)System.out.println("en WindowsCAE.java.compareOuputs OK ");
                if(DEEP_DEBUG)System.out.println("_"+judge.get(i)+"_"+contestant.get(i)+"_");
            }
        }
        if(DEEP_DEBUG)System.out.println("en WindowsCAE.java.compareOuputs AC "+AC);
        if(DEEP_DEBUG)System.out.println("en WindowsCAE.java.compareOuputs PE "+PE);
        if(DEEP_DEBUG)System.out.println("en WindowsCAE.java.compareOuputs (!AC) && PE "+((!AC) && PE));
        if( (!AC) && PE )
            return EnumVerdicts.PRESENTATION_ERROR.value();
        if( AC )
            return EnumVerdicts.ACCEPTED.value();
        return EnumVerdicts.WRONG_ANSWER.value();
    }
    public static ArrayList<String> readFile( String fileName )
    {
        BufferedReader br;
        File file = new File(fileName);
        String cad;
        ArrayList<String> ret = new ArrayList<String>();
        if( file.exists() )
        {
            try
            {
                br = new BufferedReader(new FileReader(file));
                try
                {
                    while ((cad = br.readLine()) != null)ret.add(cad);
                }
                catch (Exception ex)
                {
                    System.err.println("Exception in WindowsCAE.readFile() 1");
                    return null;
                }
            }
            catch (Exception ex)
            {
                System.err.println("Exception in WindowsCAE.readFile() 2");
                return null;
            }
        }
        else
        {
            return null;
        }
        return ret;
    }
    public boolean isCompilationSuccessfull() {
        return compilationSuccessfull;
    }

    public String getLanguaje() {
        return languaje;
    }

    public EnumStatus getStatus() {
        return status;
    }

    public VOSubmission getVOSubmission() {
        return submission;
    }

    public ArrayList<VOTestcase> getTestCases() {
        return testCases;
    }

    public EnumVerdicts getVerdict() {
        return verdict;
    }
    public int getMaxExcecutionTimeInMiliSeconds() {
        return maxExcecutionTimeInMiliSeconds;
    }

    public int getProblemTimeLimitInSeconds() {
        return problemTimeLimitInSeconds;
    }
    public String getSourceCode() {
        return this.sourceCode;
    }
    public String getCompilationOutput() {
        return this.compilationOutput;
    }
    public String getExecutionOutput() {
        return this.executionOutput;
    }
}